using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.ObjectBuilder;

namespace Unity.Extensions
{
    public class VirtualInterceptionStrategy : BuilderStrategy
    {
        static NamedTypeBuildKey InterceptClass(IBuilderContext context,
                                                Type typeToBuild,
                                                IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers,
                                                object[] originalParameters)
        {
            // Create a wrapper class which derives from the intercepted class
            typeToBuild = VirtualInterceptor.WrapClass(typeToBuild);

            // Create the proxy that's used by the wrapper
            ILEmitProxy proxy = new ILEmitProxy(handlers);

            // Create a new policy which calls the proper constructor
            List<Type> newParameterTypes = new List<Type>();
            List<InjectionParameter> newIParameters = new List<InjectionParameter>();

            newParameterTypes.Add(typeof(ILEmitProxy));
            newIParameters.Add(new InjectionParameter(proxy));

            foreach (object obj in originalParameters)
            {
                newParameterTypes.Add(obj.GetType());
                newIParameters.Add(new InjectionParameter(obj.GetType(), obj));
            }

            ConstructorInfo newConstructor = typeToBuild.GetConstructor(newParameterTypes.ToArray());
            SpecifiedConstructorSelectorPolicy newPolicy =
                new SpecifiedConstructorSelectorPolicy(newConstructor, newIParameters.ToArray());

            NamedTypeBuildKey key = new NamedTypeBuildKey(typeToBuild);
            context.Policies.Set<IConstructorSelectorPolicy>(newPolicy, key);

            // Return the key for building
            return key;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            IConstructorSelectorPolicy creationPolicy = context.Policies.Get<IConstructorSelectorPolicy>(context.BuildKey);
            IVirtualInterceptionPolicy interceptionPolicy = context.Policies.Get<IVirtualInterceptionPolicy>(context.BuildKey);
            Type typeToBuild;

            if (creationPolicy != null &&
                interceptionPolicy != null &&
                BuildKey.TryGetType(context.BuildKey, out typeToBuild))
            {
                SelectedConstructor ctor = creationPolicy.SelectConstructor(context);
                string[] keys = ctor.GetParameterKeys();
                int count = keys.GetLength(0);
                object[] ctorParams = new object[count];
                for (int idx = 0; idx < count; idx++)
                {
                    ctorParams[idx] = context.Policies.Get<IDependencyResolverPolicy>(keys[idx]).Resolve(context);
                }

                context.BuildKey = InterceptClass(context, typeToBuild, interceptionPolicy, ctorParams);
            }

            base.PreBuildUp(context);
        }
    }
}